How well does the size of a condominium in New York City explain sale price?
In this project we’ll explore how well the size of a condominium (measured in gross square feet) explains, or predicts, sale price in New York City. We will also explore how well the size of a condominium predicts sale price in each of the five boroughs of New York City: the Bronx, Brooklyn, Manhattan, Staten Island, and Queens.
Before we build linear regression models we will plot sale price versus gross square feet to see if the data exhibits any obvious visual patterns. Plotting the data will also allow us to visualize outliers, and we will investigate some of the outliers to determine if the data was recorded correctly.
library(tidyverse)
library(readxl)
library(magrittr)
Understanding the Data
glimpse(NYC_property_sales)
Rows: 38,177
Columns: 20
$ borough [3m[38;5;246m<chr>[39m[23m "Bronx", "Bronx…
$ neighborhood [3m[38;5;246m<chr>[39m[23m "Bathgate", "Ba…
$ building_class_category [3m[38;5;246m<chr>[39m[23m "01 One Family …
$ tax_class_at_present [3m[38;5;246m<chr>[39m[23m "1", "1", "1", …
$ block [3m[38;5;246m<dbl>[39m[23m 3030, 3030, 303…
$ lot [3m[38;5;246m<dbl>[39m[23m 62, 70, 63, 55,…
$ building_class_at_present [3m[38;5;246m<chr>[39m[23m "A1", "A1", "A1…
$ address [3m[38;5;246m<chr>[39m[23m "4463 Park Aven…
$ apartment_number [3m[38;5;246m<chr>[39m[23m NA, NA, NA, NA,…
$ zip_code [3m[38;5;246m<dbl>[39m[23m 10457, 10457, 1…
$ residential_units [3m[38;5;246m<dbl>[39m[23m 1, 1, 1, 1, 1, …
$ commercial_units [3m[38;5;246m<dbl>[39m[23m 0, 0, 0, 0, 0, …
$ total_units [3m[38;5;246m<dbl>[39m[23m 1, 1, 1, 1, 1, …
$ land_square_feet [3m[38;5;246m<dbl>[39m[23m 1578, 1694, 165…
$ gross_square_feet [3m[38;5;246m<dbl>[39m[23m 1470, 1497, 129…
$ year_built [3m[38;5;246m<dbl>[39m[23m 1899, 1899, 191…
$ tax_class_at_time_of_sale [3m[38;5;246m<dbl>[39m[23m 1, 1, 1, 1, 1, …
$ building_class_at_time_of_sale [3m[38;5;246m<chr>[39m[23m "A1", "A1", "A1…
$ sale_price [3m[38;5;246m<dbl>[39m[23m 455000, 388500,…
$ sale_date [3m[38;5;246m<dttm>[39m[23m 2018-11-28, 20…
For this project we will only work with a single type of building class (“R4”), a condominium residential unit in a building with an elevator. This building class is the most common building class in this NYC_property_sales dataframe.
Explore Bivariate Relationships with Scatterplots
Now that the data is loaded, processed, and ready to analyze we will use scatterplots to visualize the relationships between condominium sale price and size. The scatterplot below depicts sale price versus size for all five New York City boroughs, combined. In general we see a trend that larger condominiums are associated with a higher sale price. The data follows a somewhat linear pattern. There is no obvious curvature with the shape of the data, but there is a fair amount of spread. The strength of the bivariate relationship is moderate.
ggplot(data = NYC_condos,
aes(x = gross_square_feet, y = sale_price)) +
geom_point(aes(color = borough), alpha = 0.3) +
scale_y_continuous(labels = scales::comma, limits = c(0, 75000000)) +
xlim(0, 10000) +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal() +
labs(title = "Condominium Sale Price in NYC Generally Increases with Size",
x = "Size (Gross Square Feet)",
y = "Sale Price (USD)")

Zooming in on a smaller subset of the data, we observe the same trend below that in general, as the size of a condominium increases, so does the sale price. The pattern is somewhat linear, but there is a fair amount of spread, or dispersion, that becomes more pronounced with an increase in condominium size.
library(ggplot2)
ggplot(data = NYC_condos,
aes(x = gross_square_feet, y = sale_price)) +
geom_point(aes(color = borough), alpha = 0.3) +
scale_y_continuous(labels = scales::comma, limits = c(0, 20000000)) +
xlim(0, 5000) +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal() +
labs(title = "Condominium Sale Price in NYC Generally Increases with Size",
x = "Size (Gross Square Feet)",
y = "Sale Price (USD)")

To better visualize the spread of data for each borough, we use y-axis and x-axis scales that are specific to each borough. What neighborhoods have outliers that we should investigate?
ggplot(data = NYC_condos,
aes(x = gross_square_feet, y = sale_price)) +
geom_point(alpha = 0.3) +
facet_wrap(~ borough, scales = "free", ncol = 2) +
scale_y_continuous(labels = scales::comma) +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal() +
labs(title = "Condominium Sale Price in NYC Generally Increases with Size",
x = "Size (Gross Square Feet)",
y = "Sale Price (USD)")

Looking at the plot above, we see that, in general, larger condominiums are associated with a higher sale price in each borough. The data follows a somewhat linear pattern in each plot. But the spread is difficult to see with the Manhattan scatterplot, potentially because of the property sale of around $200 million visible to the far-right which may be impacting the visualization. There is no obvious curvature with the shape of the data, for any borough. The strength of the bivariate relationship is moderate for most boroughs, except for the Queens borough which looks to have a weaker relationship between sale price and size.
We begin our investigation of outliers by sorting all sale records by sale price, from high to low.
NYC_condos %>%
arrange(desc(sale_price)) %>%
head
Research of the highest price listing in the dataset reveals that this property sale was actually the most expensive home ever sold in the United States at the time of the sale. The luxurious building that this particular unit is located in even has its own Wikipedia page.
The real estate transaction with the second-highest sale price in this dataset was also news worthy.
These two most expensive property sales also happen to be the two largest in terms of gross square footage. We will remove the second-highest listing at 165 East 66th Street because this transaction looks to be for an entire block of residences. We would like to limit this analysis to transactions of single units.
# Make copy of dataframe before removing any sale records
NYC_condos_original <- NYC_condos
# Remove 165 East 66th Street sale record
NYC_condos <- NYC_condos %>%
filter(address != "165 East 66th St, Resi")
How well does gross square feet explain sale price for all records combined?
Next we’ll take a look at the highest sale price observations in Brooklyn. There are a number of sale records at a sale price of around $30 Million, but there is only a single observations in the range of $10 to $30 Million. Could this be correct?
NYC_condos %>%
filter(borough == "Brooklyn") %>%
arrange(desc(sale_price))
Looking through the results we see that there are approximately 40 sales records with a price of $29,620,207. This price point appears to be unusual for Brooklyn. Scrolling through the results using the viewer in R Studio we also see that all 40 property sales took place on the same day, 2019-04-08. This indicates that a transaction took place on this date where all 40 units were purchased for a TOTAL price of $29,620,207, not $29,620,207 per unit.
Thanks to the internet it does not take long for us to find information about this new building. Sure enough, this building contains 40 total units. But according to the website, the average price per unit for the 26 “active sales” is around $990,000 and the average price for the 14 previous sales is around $816,000, per unit.
For our purposes we will remove all 40 observations from the dataset because sale prices for each unit are erroneous. We could consider other ways of correcting the data. One option is to determine the price-per-square-foot by dividing the $29M sale price by the total number of square feet sold across all 40 units, and then using this number to assign a price to each unit based on its size. But that is not worth our time and we can’t be certain that method would yield valid results.
Fortunately, we have a programmatic option for surfacing potential multi-unit sales where each sale record contains the sale price for the entire real estate deal, not the price for the individual unit. Below we build a grouped filter that returns all sale records with three or more observations that have the same sale price and sale date. In general, multi-unit sales contain the same price and sale date across many sale records. When building a grouped filter we want to be careful not to “over-filter” by making the criteria too specific. In our case it looks like the filter effectively surfaces multi-sale transactions using only two grouping parameters: sale_price and sale_date.
We researched many of the addresses listed in the multi-unit-sales dataframe and confirmed that most of the sale records included here are part of a multi-unit transaction. We do not expect this filter to be 100 percent accurate, for example there may be a few property sales included here that are not part of a multi-unit sale. But overall, this grouped filter appears to be effective.
There are many ways to remove the multi-unit sales from the NYC_condos dataframe. Below are two identical methods: (1) filter for only the sale records we wish to retain that have two or less instances of sale_price and sale_date, or (2) use an anti-join to drop all records from NYC_condos found in multi_unit_sales.
NYC_condos <- NYC_condos %>%
group_by(sale_price, sale_date) %>%
filter(n() <= 2) %>%
ungroup()
# Alternative method
NYC_condos <- NYC_condos %>%
anti_join(multi_unit_sales)
Linear Regression Model for Boroughs in New York City Combined
Now that we’ve removed many multi-unit sales from the dataset, let’s generate a linear regression model for all New York City neighborhoods combined. As a reminder, we are predicting sale_price on the basis of gross_square_feet.
NYC_condos_lm <- lm(sale_price ~ gross_square_feet, data = NYC_condos)
summary(NYC_condos_lm)
Call:
lm(formula = sale_price ~ gross_square_feet, data = NYC_condos)
Residuals:
Min 1Q Median 3Q
-19865368 -840026 156635 938585
Max
140321290
Coefficients:
Estimate Std. Error
(Intercept) -3.110e+06 5.709e+04
gross_square_feet 4.462e+03 3.947e+01
t value Pr(>|t|)
(Intercept) -54.47 <2e-16 ***
gross_square_feet 113.04 <2e-16 ***
---
Signif. codes:
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2945000 on 7944 degrees of freedom
Multiple R-squared: 0.6166, Adjusted R-squared: 0.6166
F-statistic: 1.278e+04 on 1 and 7944 DF, p-value: < 2.2e-16
confint(NYC_condos_lm)
2.5 % 97.5 %
(Intercept) -3221775.590 -2997955.090
gross_square_feet 4384.254 4538.999
How does this compare to the NYC_condos_original dataframe that includes multi-unit sales?
NYC_condos_original_lm <- lm(sale_price ~ gross_square_feet, data = NYC_condos_original)
summary(NYC_condos_original_lm)
Call:
lm(formula = sale_price ~ gross_square_feet, data = NYC_condos_original)
Residuals:
Min 1Q Median 3Q
-79533546 -1211195 -860173 -251714
Max
211550415
Coefficients:
Estimate Std. Error t value
(Intercept) 940683.39 57703.86 16.30
gross_square_feet 1192.72 19.43 61.39
Pr(>|t|)
(Intercept) <2e-16 ***
gross_square_feet <2e-16 ***
---
Signif. codes:
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 4745000 on 8094 degrees of freedom
Multiple R-squared: 0.3177, Adjusted R-squared: 0.3176
F-statistic: 3769 on 1 and 8094 DF, p-value: < 2.2e-16
confint(NYC_condos_original_lm)
2.5 % 97.5 %
(Intercept) 827568.990 1053797.787
gross_square_feet 1154.636 1230.802
Comparison of linear modeling results
A bivariate linear regression of sale_price (price) explained by gross_square_feet (size) was performed on two different datasets containing condominium sale records for New York City. One dataset, NYC_condos, was cleaned to remove multi-unit sale records (where the same sale price is recorded for many units). The other dataset, NYC_condos_original, remained unaltered and contained all original sale records. In each case, the (alternative ?) hypothesis is that there is a relationship between the size of a condominium (gross_square_feet) and the price (sale_price). We can declare there is a relationship between condominium size and price when the slope is sufficiently far from zero.
For each model, the t-statistic was high enough, and the p-value was low enough (to reject null hypothesis), to declare that there is, in fact, a relationship between gross_square_feet and sale_price. The t-statistic for the cleaned dataset (NYC_condos) was nearly double that of the original dataset (NYC_condos_original) at 113.04 versus 61.39. In each case the p-value was well below the 0.05 cutoff for significance meaning that it is extremely unlikely that the relationship between condominium size and sale price is due to random chance (random chance means happens without any influence or happens naturally).
The confidence interval for the slope is [4384.254, 4538.999] for the NYC_condos dataset compared to only [1154.636, 1230.802] for the NYC_condos_original dataset.(Confidence intervals are useful for giving us an idea of the range of values we can expect to see for each coefficient, β0^ and β1^. Here, it means that on average, an increase in the gross_square_feet of by 1 feet will result in an increase in sale_price somewhere between $4384.254 and $4538.999 for NYC_condos and between $1154.636 and $1230.802 in Nyc_condos_original. ) This difference can likely be attributed to the removal of many multi-million dollar sale records for smaller units which impacted price predictions in the original dataset. The measure for lack of fit, or residual standard error (RSE) was lower for the cleaned dataset at 2,945,000 compared to 4,745,000 for the original dataset. However, it must be noted that the NYC_condos is smaller than the NYC_condos_original by 150 observations. Finally, the R-squared, or the proportion of the variability in sale_price that can be explained by gross_square_feet is 0.6166 for the cleaned NYC_condos. This is nearly double the R-squared value estimated for the NYC_condos_original dataset at 0.3177.(Bigger the R squared value, better the possibility of explaining the real scenario)
Updated scatterplot for NYC_Condos
Below is the updated scatterplot that uses the cleaned NYC_condos data. For the Brooklyn borough we are better able to see the spread of the data and how the trend line fits the data because we removed the $30 million outliers. The same is true for the Manhattan borough because the $200 million epensive house and some duplicate entreis for multiple unit sales were removed.
ggplot(data = NYC_condos,
aes(x = gross_square_feet, y = sale_price)) +
geom_point(alpha = 0.3) +
facet_wrap(~ borough, scales = "free", ncol = 2) +
scale_y_continuous(labels = scales::comma) +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal() +
labs(title = "Condominium Sale Price in NYC Generally Increases with Size",
x = "Size (Gross Square Feet)",
y = "Sale Price (USD)")

Linear Regression Models for each Borough - Coefficient Estimates
Now let’s apply the broom workflow to compare coefficient estimates across the five boroughs. The general workflow using broom and tidyverse tools to generate many models involves 4 steps:
- Nest a dataframe by a categorical variable with the
nest() function from the tidyr package - we will nest by borough.
- Fit models to nested dataframes with the
map() function from the purrr package.
- Apply the
broom functions tidy(), augment(), and/or glance() using each nested model - we’ll work with tidy() first.
- Return a tidy dataframe with the
unnest() function - this allows us to see the results.
NYC_nested <- NYC_condos %>%
group_by(borough) %>%
nest()
print(NYC_nested)
The NYC_condos dataframe was collapsed from 7,946 observations to only 5. The nesting process isolated the sale records for each borough into separate dataframes.
We can extract and inspect the values of any nested dataframe. Below is a look at the first six rows for Manhattan.
# View first few rows for Manhattan
head(NYC_nested$data[[3]])
The next step in the process is to fit a linear model to each individual dataframe. What this means is that we are generating separate linear models for each borough individually.
# Step 2: fit linear models to each borough, individually
NYC_coefficients <- NYC_nested %>%
mutate(linear_model = map(.x = data,
.f = ~lm(sale_price ~ gross_square_feet,
data = .)))
Taking a look at the data structure we see that we have a new list-column called linear_model that contains a linear model object for each borough.
# Inspect the data structure
print(NYC_coefficients)
We can view the linear modeling results for any one of the nested objects using the summary() function. Below are the linear regression statistics for Manhattan.
# Verify model results for Manhattan
summary(NYC_coefficients$linear_model[[3]])
Call:
lm(formula = sale_price ~ gross_square_feet, data = .)
Residuals:
Min 1Q Median 3Q Max
-21128191 -1001860 224235 1103543 134333578
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -3.254e+06 8.584e+04 -37.91 <2e-16 ***
gross_square_feet 4.728e+03 5.178e+01 91.31 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 3588000 on 4817 degrees of freedom
Multiple R-squared: 0.6338, Adjusted R-squared: 0.6337
F-statistic: 8337 on 1 and 4817 DF, p-value: < 2.2e-16
A quick look at the R-squared value for the Manhattan linear model(0.6338) indicates that gross_square_feet looks to be a fairly good single predictor of sale_price. Almost two-thirds of the variability with sale_price is explained by gross_square_feet.
The next step is to transform these linear model summary statistics into a tidy format.
# Step 3: generate a tidy dataframe of coefficient estimates that includes confidence intervals
library(broom)
library(purrr)
NYC_coefficients <- NYC_coefficients %>%
mutate(tidy_coefficients = map(.x = linear_model,
.f = tidy,
conf.int = TRUE))
NYC_coefficients
NA
Now we have a new variable called tidy_coefficients that contains tidy coefficient estimates for each of the five boroughs. These tidy statistics are currently stored in five separate dataframes. Below are the coefficient estimates for Manhattan.
# Inspect the results for Manhattan
print(NYC_coefficients$tidy_coefficients[[3]])
Now we can unnest the tidy_coefficients variable into a single dataframe that includes coefficient estimates for each of New York City’s five boroughs.
We’re mainly interested in the slope which explains the change in y (sale price) for each unit change in x (square footage). We can filter for the slope estimate only as follows.
# Filter to return the slope estimate only
NYC_slope <- NYC_coefficients_tidy %>%
filter(term == "gross_square_feet") %>%
arrange(estimate)
print(NYC_slope)
We’ve arranged the results in ascending order by the slope estimate. For each of the five boroughs, the t-statistic and p-value indicate that there is a relationship between sale_price and gross_square_feet. In Staten Island, an increase in square footage by one unit is estimated to increase the sale price by about $288, on average. In contrast, in Manhattan, an increase in total square footage by one unit is estimated to result in an increase in sale price of about $4,728, on average.
Linear Regression Models for each Borough - Regression Summary Statistics
Now we will apply the same workflow using broom tools to generate tidy regression summary statistics for each of the five boroughs. Below we follow the same process as we saw previously with the tidy() function, but instead we use the glance() function.
# Generate a tidy dataframe of regression summary statistics
NYC_summary_stats <- NYC_condos %>%
group_by(borough) %>%
nest() %>%
mutate(linear_model = map(.x = data,
.f = ~lm(sale_price ~ gross_square_feet,
data = .))) %>%
mutate(tidy_summary_stats = map(.x = linear_model,
.f = glance))
print(NYC_summary_stats)
Now we have a new variable called tidy_summary_stats that contains tidy regression summary statistics for each of the five boroughs in New York City. These tidy statistics are currently stored in five separate dataframes. Below we unnest the five dataframes to a single, tidy dataframe arranged by R-squared value.
# Unnest to a tidy dataframe of
NYC_summary_stats_tidy <- NYC_summary_stats %>%
select(borough, tidy_summary_stats) %>%
unnest(cols = tidy_summary_stats) %>%
arrange(r.squared)
print(NYC_summary_stats_tidy)
Looking at the r-squared values, we see that for some boroughs like Bronx and Manhattan, where r-squared is more than .55, concludes that Gross_square_feet is a good predictor for sale_price, but in other boroughs, Groos_square_feet might not be a good predictor for the sale price due to their low r-squared value.
Conclusion
Our analysis showed that, in general, the gross_square_feet variable is useful for explaining, or estimating, sale_price for condominiums in New York City. We observed that removing multi-unit sales from the dataset increased model accuracy. With linear models generated for New York City as a whole, and with linear models generated for each borough individually, we observed in all cases that the t-statistic was high enough, and the p-value was low enough, to declare that there is a relationship between gross_square_feet and sale_price.
For the linear models that we generated for each individual borough, we observed a wide range in slope estimates. The slope estimate for Manhattan was much higher than the estimate for any of the other boroughs. We did not remove the record-setting $240 million property sale from the dataset, but future analysis should investigate the impacts that this single listing has on modeling results.
Finally, regression summary statistics indicate that gross_square_feet is a better single predictor of sale_price in some boroughs versus others. For example, the R-squared value was estimated at approximately 0.63 in Manhattan, and 0.59 in Brooklyn, compared to an estimate of only 0.35 in Queens. These differences in R-squared correspond with the scatterplots generated for each borough; the strength of sale prices versus gross square feet was higher, and the dispersion (spread), was lower for Manhattan and Brooklyn as compared to Queens where the relationship was noticeably weaker because the data was more spread out.
LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgQ29uZG9taW5pdW0gU2FsZXMgUHJpY2UiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQoqKkhvdyB3ZWxsIGRvZXMgdGhlIHNpemUgb2YgYSBjb25kb21pbml1bSBpbiBOZXcgWW9yayBDaXR5IGV4cGxhaW4gc2FsZSBwcmljZT8qKg0KDQpJbiB0aGlzIHByb2plY3Qgd2UnbGwgZXhwbG9yZSBob3cgd2VsbCB0aGUgc2l6ZSBvZiBhIGNvbmRvbWluaXVtIChtZWFzdXJlZCBpbiBncm9zcyBzcXVhcmUgZmVldCkgZXhwbGFpbnMsIG9yIHByZWRpY3RzLCBzYWxlIHByaWNlIGluIE5ldyBZb3JrIENpdHkuIFdlIHdpbGwgYWxzbyBleHBsb3JlIGhvdyB3ZWxsIHRoZSBzaXplIG9mIGEgY29uZG9taW5pdW0gcHJlZGljdHMgc2FsZSBwcmljZSBpbiBlYWNoIG9mIHRoZSBmaXZlIGJvcm91Z2hzIG9mIE5ldyBZb3JrIENpdHk6IHRoZSBCcm9ueCwgQnJvb2tseW4sIE1hbmhhdHRhbiwgU3RhdGVuIElzbGFuZCwgYW5kIFF1ZWVucy4gDQoNCkJlZm9yZSB3ZSBidWlsZCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMgd2Ugd2lsbCBwbG90IHNhbGUgcHJpY2UgdmVyc3VzIGdyb3NzIHNxdWFyZSBmZWV0IHRvIHNlZSBpZiB0aGUgZGF0YSBleGhpYml0cyBhbnkgb2J2aW91cyB2aXN1YWwgcGF0dGVybnMuIFBsb3R0aW5nIHRoZSBkYXRhIHdpbGwgYWxzbyBhbGxvdyB1cyB0byB2aXN1YWxpemUgb3V0bGllcnMsIGFuZCB3ZSB3aWxsIGludmVzdGlnYXRlIHNvbWUgb2YgdGhlIG91dGxpZXJzIHRvIGRldGVybWluZSBpZiB0aGUgZGF0YSB3YXMgcmVjb3JkZWQgY29ycmVjdGx5LiANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShtYWdyaXR0cikNCmBgYA0KDQojIFVuZGVyc3RhbmRpbmcgdGhlIERhdGENCg0KDQoNCmBgYHtyfQ0KTllDX3Byb3BlcnR5X3NhbGVzIDwtIHJlYWRfY3N2KCdOWUNfcHJvcGVydHlfc2FsZXMuY3N2JykNCk5ZQ19wcm9wZXJ0eV9zYWxlcw0KYGBgDQoNCmBgYHtyfQ0KZ2xpbXBzZShOWUNfcHJvcGVydHlfc2FsZXMpDQpgYGANCg0KRm9yIHRoaXMgcHJvamVjdCB3ZSB3aWxsIG9ubHkgd29yayB3aXRoIGEgc2luZ2xlIHR5cGUgb2YgYnVpbGRpbmcgY2xhc3MgKCJSNCIpLCBhIGNvbmRvbWluaXVtIHJlc2lkZW50aWFsIHVuaXQgaW4gYSBidWlsZGluZyB3aXRoIGFuIGVsZXZhdG9yLiBUaGlzIGJ1aWxkaW5nIGNsYXNzIGlzIHRoZSBtb3N0IGNvbW1vbiBidWlsZGluZyBjbGFzcyBpbiB0aGlzIGBOWUNfcHJvcGVydHlfc2FsZXNgIGRhdGFmcmFtZS4gDQpgYGB7cn0NCk5ZQ19jb25kb3MgPC0gTllDX3Byb3BlcnR5X3NhbGVzICU+JSANCiAgIyBGaWx0ZXIgdG8gaW5jbHVkZSBvbmx5IHByb3BlcnR5IHR5cGU6IENPTkRPOyBSRVNJREVOVElBTCBVTklUIElOIEVMRVZBVE9SIEJMREcuDQogIA0KICBmaWx0ZXIoYnVpbGRpbmdfY2xhc3NfYXRfdGltZV9vZl9zYWxlID09ICJSNCIpDQoNCk5ZQ19jb25kb3MNCmBgYA0KDQojIEV4cGxvcmUgQml2YXJpYXRlIFJlbGF0aW9uc2hpcHMgd2l0aCBTY2F0dGVycGxvdHMNCg0KTm93IHRoYXQgdGhlIGRhdGEgaXMgbG9hZGVkLCBwcm9jZXNzZWQsIGFuZCByZWFkeSB0byBhbmFseXplIHdlIHdpbGwgdXNlIHNjYXR0ZXJwbG90cyB0byB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBjb25kb21pbml1bSBzYWxlIHByaWNlIGFuZCBzaXplLiBUaGUgc2NhdHRlcnBsb3QgYmVsb3cgZGVwaWN0cyBzYWxlIHByaWNlIHZlcnN1cyBzaXplIGZvciBhbGwgZml2ZSBOZXcgWW9yayBDaXR5IGJvcm91Z2hzLCBjb21iaW5lZC4gSW4gZ2VuZXJhbCB3ZSBzZWUgYSB0cmVuZCB0aGF0IGxhcmdlciBjb25kb21pbml1bXMgYXJlIGFzc29jaWF0ZWQgd2l0aCBhIGhpZ2hlciBzYWxlIHByaWNlLiBUaGUgZGF0YSBmb2xsb3dzIGEgc29tZXdoYXQgbGluZWFyIHBhdHRlcm4uIFRoZXJlIGlzIG5vIG9idmlvdXMgY3VydmF0dXJlIHdpdGggdGhlIHNoYXBlIG9mIHRoZSBkYXRhLCBidXQgdGhlcmUgaXMgYSBmYWlyIGFtb3VudCBvZiBzcHJlYWQuIFRoZSBzdHJlbmd0aCBvZiB0aGUgYml2YXJpYXRlIHJlbGF0aW9uc2hpcCBpcyBtb2RlcmF0ZS4gDQoNCg0KYGBge3J9DQoNCmdncGxvdChkYXRhID0gTllDX2NvbmRvcywgDQogICAgICAgYWVzKHggPSBncm9zc19zcXVhcmVfZmVldCwgeSA9IHNhbGVfcHJpY2UpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYm9yb3VnaCksIGFscGhhID0gMC4zKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hLCBsaW1pdHMgPSBjKDAsIDc1MDAwMDAwKSkgKw0KICB4bGltKDAsIDEwMDAwKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJDb25kb21pbml1bSBTYWxlIFByaWNlIGluIE5ZQyBHZW5lcmFsbHkgSW5jcmVhc2VzIHdpdGggU2l6ZSIsDQogICAgICAgeCA9ICJTaXplIChHcm9zcyBTcXVhcmUgRmVldCkiLA0KICAgICAgIHkgPSAiU2FsZSBQcmljZSAoVVNEKSIpDQpgYGANCg0KDQoNClpvb21pbmcgaW4gb24gYSBzbWFsbGVyIHN1YnNldCBvZiB0aGUgZGF0YSwgd2Ugb2JzZXJ2ZSB0aGUgc2FtZSB0cmVuZCBiZWxvdyB0aGF0IGluIGdlbmVyYWwsIGFzIHRoZSBzaXplIG9mIGEgY29uZG9taW5pdW0gaW5jcmVhc2VzLCBzbyBkb2VzIHRoZSBzYWxlIHByaWNlLiBUaGUgcGF0dGVybiBpcyBzb21ld2hhdCBsaW5lYXIsIGJ1dCB0aGVyZSBpcyBhIGZhaXIgYW1vdW50IG9mIHNwcmVhZCwgb3IgZGlzcGVyc2lvbiwgdGhhdCBiZWNvbWVzIG1vcmUgcHJvbm91bmNlZCB3aXRoIGFuIGluY3JlYXNlIGluIGNvbmRvbWluaXVtIHNpemUuDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRhdGEgPSBOWUNfY29uZG9zLCANCiAgICAgICBhZXMoeCA9IGdyb3NzX3NxdWFyZV9mZWV0LCB5ID0gc2FsZV9wcmljZSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBib3JvdWdoKSwgYWxwaGEgPSAwLjMpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEsIGxpbWl0cyA9IGMoMCwgMjAwMDAwMDApKSArDQogIHhsaW0oMCwgNTAwMCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiQ29uZG9taW5pdW0gU2FsZSBQcmljZSBpbiBOWUMgR2VuZXJhbGx5IEluY3JlYXNlcyB3aXRoIFNpemUiLA0KICAgICAgIHggPSAiU2l6ZSAoR3Jvc3MgU3F1YXJlIEZlZXQpIiwNCiAgICAgICB5ID0gIlNhbGUgUHJpY2UgKFVTRCkiKQ0KYGBgDQoNCg0KVG8gYmV0dGVyIHZpc3VhbGl6ZSB0aGUgc3ByZWFkIG9mIGRhdGEgZm9yIGVhY2ggYm9yb3VnaCwgd2UgdXNlIHktYXhpcyBhbmQgeC1heGlzIHNjYWxlcyB0aGF0IGFyZSBzcGVjaWZpYyB0byBlYWNoIGJvcm91Z2guIFdoYXQgbmVpZ2hib3Job29kcyBoYXZlIG91dGxpZXJzIHRoYXQgd2Ugc2hvdWxkIGludmVzdGlnYXRlPyANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IE5ZQ19jb25kb3MsIA0KICAgICAgIGFlcyh4ID0gZ3Jvc3Nfc3F1YXJlX2ZlZXQsIHkgPSBzYWxlX3ByaWNlKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC4zKSArDQogIGZhY2V0X3dyYXAofiBib3JvdWdoLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSAyKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJDb25kb21pbml1bSBTYWxlIFByaWNlIGluIE5ZQyBHZW5lcmFsbHkgSW5jcmVhc2VzIHdpdGggU2l6ZSIsDQogICAgICAgeCA9ICJTaXplIChHcm9zcyBTcXVhcmUgRmVldCkiLA0KICAgICAgIHkgPSAiU2FsZSBQcmljZSAoVVNEKSIpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgcGxvdCBhYm92ZSwgd2Ugc2VlIHRoYXQsIGluIGdlbmVyYWwsIGxhcmdlciBjb25kb21pbml1bXMgYXJlIGFzc29jaWF0ZWQgd2l0aCBhIGhpZ2hlciBzYWxlIHByaWNlIGluIGVhY2ggYm9yb3VnaC4gVGhlIGRhdGEgZm9sbG93cyBhIHNvbWV3aGF0IGxpbmVhciBwYXR0ZXJuIGluIGVhY2ggcGxvdC4gQnV0IHRoZSBzcHJlYWQgaXMgZGlmZmljdWx0IHRvIHNlZSB3aXRoIHRoZSBNYW5oYXR0YW4gc2NhdHRlcnBsb3QsIHBvdGVudGlhbGx5IGJlY2F1c2Ugb2YgdGhlIHByb3BlcnR5IHNhbGUgb2YgYXJvdW5kICQyMDAgbWlsbGlvbiB2aXNpYmxlIHRvIHRoZSBmYXItcmlnaHQgd2hpY2ggbWF5IGJlIGltcGFjdGluZyB0aGUgdmlzdWFsaXphdGlvbi4gVGhlcmUgaXMgbm8gb2J2aW91cyBjdXJ2YXR1cmUgd2l0aCB0aGUgc2hhcGUgb2YgdGhlIGRhdGEsIGZvciBhbnkgYm9yb3VnaC4gVGhlIHN0cmVuZ3RoIG9mIHRoZSBiaXZhcmlhdGUgcmVsYXRpb25zaGlwIGlzIG1vZGVyYXRlIGZvciBtb3N0IGJvcm91Z2hzLCBleGNlcHQgZm9yIHRoZSBRdWVlbnMgYm9yb3VnaCB3aGljaCBsb29rcyB0byBoYXZlIGEgd2Vha2VyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNhbGUgcHJpY2UgYW5kIHNpemUuIA0KDQpXZSBiZWdpbiBvdXIgaW52ZXN0aWdhdGlvbiBvZiBvdXRsaWVycyBieSBzb3J0aW5nIGFsbCBzYWxlIHJlY29yZHMgYnkgc2FsZSBwcmljZSwgZnJvbSBoaWdoIHRvIGxvdy4NCg0KYGBge3J9DQpOWUNfY29uZG9zICU+JSANCiAgYXJyYW5nZShkZXNjKHNhbGVfcHJpY2UpKSAlPiUgDQogIGhlYWQNCmBgYA0KDQpSZXNlYXJjaCBvZiB0aGUgaGlnaGVzdCBwcmljZSBsaXN0aW5nIGluIHRoZSBkYXRhc2V0IHJldmVhbHMgdGhhdCB0aGlzIHByb3BlcnR5IHNhbGUgd2FzIGFjdHVhbGx5IHRoZSBbbW9zdCBleHBlbnNpdmUgaG9tZSBldmVyIHNvbGQgaW4gdGhlIFVuaXRlZCBTdGF0ZXNdKGh0dHBzOi8vd3d3LjZzcWZ0LmNvbS9iaWxsaW9uYWlyZS1rZW4tZ3JpZmZpbi1idXlzLTIzOG0tbnljLXBlbnRob3VzZS10aGUtbW9zdC1leHBlbnNpdmUtaG9tZS1zb2xkLWluLXRoZS11LXMvKSBhdCB0aGUgdGltZSBvZiB0aGUgc2FsZS4gVGhlIGx1eHVyaW91cyBidWlsZGluZyB0aGF0IHRoaXMgcGFydGljdWxhciB1bml0IGlzIGxvY2F0ZWQgaW4gZXZlbiBoYXMgaXRzIG93biBbV2lraXBlZGlhIHBhZ2VdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpLzIyMF9DZW50cmFsX1BhcmtfU291dGgpLiANCg0KVGhlIHJlYWwgZXN0YXRlIHRyYW5zYWN0aW9uIHdpdGggdGhlIHNlY29uZC1oaWdoZXN0IHNhbGUgcHJpY2UgaW4gdGhpcyBkYXRhc2V0IHdhcyBhbHNvIFtuZXdzIHdvcnRoeV0oaHR0cHM6Ly90aGVyZWFsZGVhbC5jb20vMjAxOS8wNC8xMi9jaW0tZ3JvdXAtYWNxdWlyZXMtcmVzaS1wb3J0aW9uLW9mLXVlcy1sdXh1cnktcmVudGFsLWZvci0yMDBtLykuDQoNClRoZXNlIHR3byBtb3N0IGV4cGVuc2l2ZSBwcm9wZXJ0eSBzYWxlcyBhbHNvIGhhcHBlbiB0byBiZSB0aGUgdHdvIGxhcmdlc3QgaW4gdGVybXMgb2YgZ3Jvc3Mgc3F1YXJlIGZvb3RhZ2UuIFdlIHdpbGwgcmVtb3ZlIHRoZSBzZWNvbmQtaGlnaGVzdCBsaXN0aW5nIGF0IDE2NSBFYXN0IDY2dGggU3RyZWV0IGJlY2F1c2UgKip0aGlzIHRyYW5zYWN0aW9uIGxvb2tzIHRvIGJlIGZvciBhbiBlbnRpcmUgYmxvY2sgb2YgcmVzaWRlbmNlcy4qKiBXZSB3b3VsZCBsaWtlIHRvIGxpbWl0IHRoaXMgYW5hbHlzaXMgdG8gdHJhbnNhY3Rpb25zIG9mIHNpbmdsZSB1bml0cy4NCg0KYGBge3J9DQojIE1ha2UgY29weSBvZiBkYXRhZnJhbWUgYmVmb3JlIHJlbW92aW5nIGFueSBzYWxlIHJlY29yZHMNCk5ZQ19jb25kb3Nfb3JpZ2luYWwgPC0gTllDX2NvbmRvcw0KIyBSZW1vdmUgMTY1IEVhc3QgNjZ0aCBTdHJlZXQgc2FsZSByZWNvcmQNCk5ZQ19jb25kb3MgPC0gTllDX2NvbmRvcyAlPiUgDQogIGZpbHRlcihhZGRyZXNzICE9ICIxNjUgRWFzdCA2NnRoIFN0LCBSZXNpIikNCmBgYA0KDQojIEhvdyB3ZWxsIGRvZXMgZ3Jvc3Mgc3F1YXJlIGZlZXQgZXhwbGFpbiBzYWxlIHByaWNlIGZvciBhbGwgcmVjb3JkcyBjb21iaW5lZD8NCg0KTmV4dCB3ZSdsbCB0YWtlIGEgbG9vayBhdCB0aGUgaGlnaGVzdCBzYWxlIHByaWNlIG9ic2VydmF0aW9ucyBpbiBCcm9va2x5bi4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIHNhbGUgcmVjb3JkcyBhdCBhIHNhbGUgcHJpY2Ugb2YgYXJvdW5kIFwkMzAgTWlsbGlvbiwgYnV0IHRoZXJlIGlzIG9ubHkgYSBzaW5nbGUgb2JzZXJ2YXRpb25zIGluIHRoZSByYW5nZSBvZiBcJDEwIHRvIFwkMzAgTWlsbGlvbi4gQ291bGQgdGhpcyBiZSBjb3JyZWN0Pw0KDQpgYGB7cn0NCk5ZQ19jb25kb3MgJT4lIA0KICBmaWx0ZXIoYm9yb3VnaCA9PSAiQnJvb2tseW4iKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhzYWxlX3ByaWNlKSkNCmBgYA0KDQpMb29raW5nIHRocm91Z2ggdGhlIHJlc3VsdHMgd2Ugc2VlIHRoYXQgdGhlcmUgYXJlIGFwcHJveGltYXRlbHkgNDAgc2FsZXMgcmVjb3JkcyB3aXRoIGEgcHJpY2Ugb2YgXCQyOSw2MjAsMjA3LiBUaGlzIHByaWNlIHBvaW50IGFwcGVhcnMgdG8gYmUgdW51c3VhbCBmb3IgQnJvb2tseW4uIFNjcm9sbGluZyB0aHJvdWdoIHRoZSByZXN1bHRzIHVzaW5nIHRoZSB2aWV3ZXIgaW4gUiBTdHVkaW8gd2UgYWxzbyBzZWUgdGhhdCBhbGwgNDAgcHJvcGVydHkgc2FsZXMgdG9vayBwbGFjZSBvbiB0aGUgc2FtZSBkYXksIDIwMTktMDQtMDguIFRoaXMgaW5kaWNhdGVzIHRoYXQgYSB0cmFuc2FjdGlvbiB0b29rIHBsYWNlIG9uIHRoaXMgZGF0ZSB3aGVyZSAqKmFsbCA0MCB1bml0cyB3ZXJlIHB1cmNoYXNlZCBmb3IgYSBUT1RBTCBwcmljZSBvZiBcJDI5LDYyMCwyMDcsIG5vdCBcJDI5LDYyMCwyMDcgcGVyIHVuaXQuKioNCg0KVGhhbmtzIHRvIHRoZSBpbnRlcm5ldCBpdCBkb2VzIG5vdCB0YWtlIGxvbmcgZm9yIHVzIHRvIGZpbmQgW2luZm9ybWF0aW9uIGFib3V0IHRoaXMgbmV3IGJ1aWxkaW5nXShodHRwczovL3N0cmVldGVhc3kuY29tL2J1aWxkaW5nLzU1NC00LWF2ZW51ZS1icm9va2x5bikuIFN1cmUgZW5vdWdoLCB0aGlzIGJ1aWxkaW5nIGNvbnRhaW5zIDQwIHRvdGFsIHVuaXRzLiBCdXQgYWNjb3JkaW5nIHRvIHRoZSB3ZWJzaXRlLCB0aGUgYXZlcmFnZSBwcmljZSAqcGVyIHVuaXQqIGZvciB0aGUgMjYgImFjdGl2ZSBzYWxlcyIgaXMgYXJvdW5kIFwkOTkwLDAwMCBhbmQgdGhlIGF2ZXJhZ2UgcHJpY2UgZm9yIHRoZSAxNCBwcmV2aW91cyBzYWxlcyBpcyBhcm91bmQgXCQ4MTYsMDAwLCBwZXIgdW5pdC4gDQoNCkZvciBvdXIgcHVycG9zZXMgd2Ugd2lsbCByZW1vdmUgYWxsIDQwIG9ic2VydmF0aW9ucyBmcm9tIHRoZSBkYXRhc2V0IGJlY2F1c2Ugc2FsZSBwcmljZXMgZm9yIGVhY2ggdW5pdCBhcmUgZXJyb25lb3VzLiBXZSBjb3VsZCBjb25zaWRlciBvdGhlciB3YXlzIG9mIGNvcnJlY3RpbmcgdGhlIGRhdGEuIE9uZSBvcHRpb24gaXMgdG8gZGV0ZXJtaW5lIHRoZSBwcmljZS1wZXItc3F1YXJlLWZvb3QgYnkgZGl2aWRpbmcgdGhlICQyOU0gc2FsZSBwcmljZSBieSB0aGUgdG90YWwgbnVtYmVyIG9mIHNxdWFyZSBmZWV0IHNvbGQgYWNyb3NzIGFsbCA0MCB1bml0cywgYW5kIHRoZW4gdXNpbmcgdGhpcyBudW1iZXIgdG8gYXNzaWduIGEgcHJpY2UgdG8gZWFjaCB1bml0IGJhc2VkIG9uIGl0cyBzaXplLiBCdXQgdGhhdCBpcyBub3Qgd29ydGggb3VyIHRpbWUgYW5kIHdlIGNhbid0IGJlIGNlcnRhaW4gdGhhdCBtZXRob2Qgd291bGQgeWllbGQgdmFsaWQgcmVzdWx0cy4gDQoNCkZvcnR1bmF0ZWx5LCB3ZSBoYXZlIGEgcHJvZ3JhbW1hdGljIG9wdGlvbiBmb3Igc3VyZmFjaW5nIHBvdGVudGlhbCBtdWx0aS11bml0IHNhbGVzIHdoZXJlIGVhY2ggc2FsZSByZWNvcmQgY29udGFpbnMgdGhlIHNhbGUgcHJpY2UgZm9yIHRoZSBlbnRpcmUgcmVhbCBlc3RhdGUgZGVhbCwgbm90IHRoZSBwcmljZSBmb3IgdGhlIGluZGl2aWR1YWwgdW5pdC4gQmVsb3cgd2UgYnVpbGQgYSBncm91cGVkIGZpbHRlciB0aGF0IHJldHVybnMgYWxsIHNhbGUgcmVjb3JkcyB3aXRoIHRocmVlIG9yIG1vcmUgb2JzZXJ2YXRpb25zIHRoYXQgaGF2ZSB0aGUgc2FtZSBzYWxlIHByaWNlIGFuZCBzYWxlIGRhdGUuIEluIGdlbmVyYWwsIG11bHRpLXVuaXQgc2FsZXMgY29udGFpbiB0aGUgc2FtZSBwcmljZSBhbmQgc2FsZSBkYXRlIGFjcm9zcyBtYW55IHNhbGUgcmVjb3Jkcy4gV2hlbiBidWlsZGluZyBhIGdyb3VwZWQgZmlsdGVyIHdlIHdhbnQgdG8gYmUgY2FyZWZ1bCBub3QgdG8gIm92ZXItZmlsdGVyIiBieSBtYWtpbmcgdGhlIGNyaXRlcmlhIHRvbyBzcGVjaWZpYy4gSW4gb3VyIGNhc2UgaXQgbG9va3MgbGlrZSB0aGUgZmlsdGVyIGVmZmVjdGl2ZWx5IHN1cmZhY2VzIG11bHRpLXNhbGUgdHJhbnNhY3Rpb25zIHVzaW5nIG9ubHkgdHdvIGdyb3VwaW5nIHBhcmFtZXRlcnM6IGBzYWxlX3ByaWNlYCBhbmQgYHNhbGVfZGF0ZWAuICANCg0KYGBge3J9DQptdWx0aV91bml0X3NhbGVzIDwtIE5ZQ19jb25kb3MgJT4lIA0KICBncm91cF9ieShzYWxlX3ByaWNlLCBzYWxlX2RhdGUpICU+JSANCiAgZmlsdGVyKG4oKSA+PSAzKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhzYWxlX3ByaWNlKSkNCm11bHRpX3VuaXRfc2FsZXMNCmBgYA0KDQpXZSByZXNlYXJjaGVkIG1hbnkgb2YgdGhlIGFkZHJlc3NlcyBsaXN0ZWQgaW4gdGhlIGBtdWx0aS11bml0LXNhbGVzYCBkYXRhZnJhbWUgYW5kIGNvbmZpcm1lZCB0aGF0IG1vc3Qgb2YgdGhlIHNhbGUgcmVjb3JkcyBpbmNsdWRlZCBoZXJlIGFyZSBwYXJ0IG9mIGEgbXVsdGktdW5pdCB0cmFuc2FjdGlvbi4gV2UgZG8gbm90IGV4cGVjdCB0aGlzIGZpbHRlciB0byBiZSAxMDAgcGVyY2VudCBhY2N1cmF0ZSwgZm9yIGV4YW1wbGUgdGhlcmUgbWF5IGJlIGEgZmV3IHByb3BlcnR5IHNhbGVzIGluY2x1ZGVkIGhlcmUgdGhhdCBhcmUgbm90IHBhcnQgb2YgYSBtdWx0aS11bml0IHNhbGUuIEJ1dCBvdmVyYWxsLCB0aGlzIGdyb3VwZWQgZmlsdGVyIGFwcGVhcnMgdG8gYmUgZWZmZWN0aXZlLiANCg0KVGhlcmUgYXJlIG1hbnkgd2F5cyB0byByZW1vdmUgdGhlIG11bHRpLXVuaXQgc2FsZXMgZnJvbSB0aGUgYE5ZQ19jb25kb3NgIGRhdGFmcmFtZS4gQmVsb3cgYXJlIHR3byBpZGVudGljYWwgbWV0aG9kczogKDEpIGZpbHRlciBmb3Igb25seSB0aGUgc2FsZSByZWNvcmRzIHdlIHdpc2ggdG8gKnJldGFpbiogdGhhdCBoYXZlIHR3byBvciBsZXNzIGluc3RhbmNlcyBvZiBgc2FsZV9wcmljZWAgYW5kIGBzYWxlX2RhdGVgLCBvciAoMikgdXNlIGFuIGFudGktam9pbiB0byBkcm9wIGFsbCByZWNvcmRzIGZyb20gYE5ZQ19jb25kb3NgIGZvdW5kIGluIGBtdWx0aV91bml0X3NhbGVzYC4gDQoNCg0KYGBge3J9DQpOWUNfY29uZG9zIDwtIE5ZQ19jb25kb3MgJT4lDQogIGdyb3VwX2J5KHNhbGVfcHJpY2UsIHNhbGVfZGF0ZSkgJT4lDQogIGZpbHRlcihuKCkgPD0gMikgJT4lDQogIHVuZ3JvdXAoKQ0KIyBBbHRlcm5hdGl2ZSBtZXRob2QNCk5ZQ19jb25kb3MgPC0gTllDX2NvbmRvcyAlPiUgDQogIGFudGlfam9pbihtdWx0aV91bml0X3NhbGVzKQ0KDQpgYGANCg0KDQojIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsIGZvciBCb3JvdWdocyBpbiBOZXcgWW9yayBDaXR5IENvbWJpbmVkDQoNCk5vdyB0aGF0IHdlJ3ZlIHJlbW92ZWQgbWFueSBtdWx0aS11bml0IHNhbGVzIGZyb20gdGhlIGRhdGFzZXQsIGxldCdzIGdlbmVyYXRlIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIGFsbCBOZXcgWW9yayBDaXR5IG5laWdoYm9yaG9vZHMgY29tYmluZWQuIEFzIGEgcmVtaW5kZXIsIHdlIGFyZSBwcmVkaWN0aW5nIGBzYWxlX3ByaWNlYCBvbiB0aGUgYmFzaXMgb2YgYGdyb3NzX3NxdWFyZV9mZWV0YC4NCg0KYGBge3J9DQpOWUNfY29uZG9zX2xtIDwtIGxtKHNhbGVfcHJpY2UgfiBncm9zc19zcXVhcmVfZmVldCwgZGF0YSA9IE5ZQ19jb25kb3MpICANCnN1bW1hcnkoTllDX2NvbmRvc19sbSkNCmNvbmZpbnQoTllDX2NvbmRvc19sbSkNCmBgYA0KDQpIb3cgZG9lcyB0aGlzIGNvbXBhcmUgdG8gdGhlIGBOWUNfY29uZG9zX29yaWdpbmFsYCBkYXRhZnJhbWUgdGhhdCBpbmNsdWRlcyBtdWx0aS11bml0IHNhbGVzPyANCg0KDQpgYGB7cn0NCk5ZQ19jb25kb3Nfb3JpZ2luYWxfbG0gPC0gbG0oc2FsZV9wcmljZSB+IGdyb3NzX3NxdWFyZV9mZWV0LCBkYXRhID0gTllDX2NvbmRvc19vcmlnaW5hbCkgIA0Kc3VtbWFyeShOWUNfY29uZG9zX29yaWdpbmFsX2xtKQ0KY29uZmludChOWUNfY29uZG9zX29yaWdpbmFsX2xtKQ0KYGBgDQoNCg0KIyMgQ29tcGFyaXNvbiBvZiBsaW5lYXIgbW9kZWxpbmcgcmVzdWx0cw0KDQpBIGJpdmFyaWF0ZSBsaW5lYXIgcmVncmVzc2lvbiBvZiBgc2FsZV9wcmljZWAgKHByaWNlKSBleHBsYWluZWQgYnkgYGdyb3NzX3NxdWFyZV9mZWV0YCAoc2l6ZSkgd2FzIHBlcmZvcm1lZCBvbiB0d28gZGlmZmVyZW50IGRhdGFzZXRzIGNvbnRhaW5pbmcgY29uZG9taW5pdW0gc2FsZSByZWNvcmRzIGZvciBOZXcgWW9yayBDaXR5LiBPbmUgZGF0YXNldCwgYE5ZQ19jb25kb3NgLCB3YXMgY2xlYW5lZCB0byByZW1vdmUgbXVsdGktdW5pdCBzYWxlIHJlY29yZHMgKHdoZXJlIHRoZSBzYW1lIHNhbGUgcHJpY2UgaXMgcmVjb3JkZWQgZm9yIG1hbnkgdW5pdHMpLiBUaGUgb3RoZXIgZGF0YXNldCwgYE5ZQ19jb25kb3Nfb3JpZ2luYWxgLCByZW1haW5lZCB1bmFsdGVyZWQgYW5kIGNvbnRhaW5lZCBhbGwgb3JpZ2luYWwgc2FsZSByZWNvcmRzLiBJbiBlYWNoIGNhc2UsICoqdGhlIChhbHRlcm5hdGl2ZSA/KSBoeXBvdGhlc2lzIGlzIHRoYXQgIHRoZXJlIGlzIGEgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHNpemUgb2YgYSBjb25kb21pbml1bSAoYGdyb3NzX3NxdWFyZV9mZWV0YCkgYW5kIHRoZSBwcmljZSAoYHNhbGVfcHJpY2VgKS4qKiBXZSBjYW4gZGVjbGFyZSB0aGVyZSBpcyBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGNvbmRvbWluaXVtIHNpemUgYW5kIHByaWNlIHdoZW4gdGhlIHNsb3BlIGlzIHN1ZmZpY2llbnRseSBmYXIgZnJvbSB6ZXJvLiANCg0KRm9yIGVhY2ggbW9kZWwsIHRoZSB0LXN0YXRpc3RpYyB3YXMgaGlnaCBlbm91Z2gsIGFuZCB0aGUgcC12YWx1ZSB3YXMgbG93IGVub3VnaCAodG8gcmVqZWN0IG51bGwgaHlwb3RoZXNpcyksIHRvIGRlY2xhcmUgdGhhdCB0aGVyZSBpcywgaW4gZmFjdCwgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgZ3Jvc3Nfc3F1YXJlX2ZlZXRgIGFuZCBgc2FsZV9wcmljZWAuIFRoZSB0LXN0YXRpc3RpYyBmb3IgdGhlIGNsZWFuZWQgZGF0YXNldCAoYE5ZQ19jb25kb3NgKSB3YXMgbmVhcmx5IGRvdWJsZSB0aGF0IG9mIHRoZSBvcmlnaW5hbCBkYXRhc2V0IChgTllDX2NvbmRvc19vcmlnaW5hbGApIGF0IDExMy4wNCB2ZXJzdXMgNjEuMzkuIEluIGVhY2ggY2FzZSB0aGUgKipwLXZhbHVlIHdhcyB3ZWxsIGJlbG93IHRoZSAwLjA1KiogY3V0b2ZmIGZvciBzaWduaWZpY2FuY2UgbWVhbmluZyB0aGF0IGl0IGlzIGV4dHJlbWVseSB1bmxpa2VseSB0aGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBjb25kb21pbml1bSBzaXplIGFuZCBzYWxlIHByaWNlIGlzIGR1ZSB0byByYW5kb20gY2hhbmNlIChyYW5kb20gY2hhbmNlIG1lYW5zIGhhcHBlbnMgd2l0aG91dCBhbnkgaW5mbHVlbmNlIG9yIGhhcHBlbnMgbmF0dXJhbGx5KS4gDQoNCioqVGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIHRoZSBzbG9wZSBpcyBbNDM4NC4yNTQsIDQ1MzguOTk5XSBmb3IgdGhlIGBOWUNfY29uZG9zYCBkYXRhc2V0IGNvbXBhcmVkIHRvIG9ubHkgWzExNTQuNjM2LCAxMjMwLjgwMl0gZm9yIHRoZSBgTllDX2NvbmRvc19vcmlnaW5hbGAgZGF0YXNldC4qKihDb25maWRlbmNlIGludGVydmFscyBhcmUgdXNlZnVsIGZvciBnaXZpbmcgdXMgYW4gaWRlYSBvZiB0aGUgcmFuZ2Ugb2YgdmFsdWVzIHdlIGNhbiBleHBlY3QgdG8gc2VlIGZvciBlYWNoIGNvZWZmaWNpZW50LCDOsjBeIGFuZCDOsjFeLiBIZXJlLCBpdCBtZWFucyB0aGF0IG9uIGF2ZXJhZ2UsIGFuIGluY3JlYXNlIGluIHRoZSBncm9zc19zcXVhcmVfZmVldCBvZiBieSAxIGZlZXQgd2lsbCByZXN1bHQgaW4gYW4gaW5jcmVhc2UgaW4gc2FsZV9wcmljZSBzb21ld2hlcmUgYmV0d2VlbiAkNDM4NC4yNTQgYW5kICQ0NTM4Ljk5OSBmb3IgTllDX2NvbmRvcyBhbmQgYmV0d2VlbiAkMTE1NC42MzYgYW5kICQxMjMwLjgwMiBpbiBOeWNfY29uZG9zX29yaWdpbmFsLiApIFRoaXMgZGlmZmVyZW5jZSBjYW4gbGlrZWx5IGJlIGF0dHJpYnV0ZWQgdG8gdGhlIHJlbW92YWwgb2YgbWFueSBtdWx0aS1taWxsaW9uIGRvbGxhciBzYWxlIHJlY29yZHMgZm9yIHNtYWxsZXIgdW5pdHMgd2hpY2ggaW1wYWN0ZWQgcHJpY2UgcHJlZGljdGlvbnMgaW4gdGhlIG9yaWdpbmFsIGRhdGFzZXQuIFRoZSBtZWFzdXJlIGZvciAqbGFjayBvZiBmaXQqLCBvciByZXNpZHVhbCBzdGFuZGFyZCBlcnJvciAqKihSU0UpIHdhcyBsb3dlciBmb3IgdGhlIGNsZWFuZWQgZGF0YXNldCBhdCAyLDk0NSwwMDAgY29tcGFyZWQgdG8gNCw3NDUsMDAwIGZvciB0aGUgb3JpZ2luYWwgZGF0YXNldC4qKiBIb3dldmVyLCBpdCBtdXN0IGJlIG5vdGVkIHRoYXQgdGhlIGBOWUNfY29uZG9zYCBpcyBzbWFsbGVyIHRoYW4gdGhlIGBOWUNfY29uZG9zX29yaWdpbmFsYCBieSAxNTAgb2JzZXJ2YXRpb25zLiBGaW5hbGx5LCB0aGUgKipSLXNxdWFyZWQsIG9yIHRoZSBwcm9wb3J0aW9uIG9mIHRoZSB2YXJpYWJpbGl0eSBpbiBgc2FsZV9wcmljZWAgdGhhdCBjYW4gYmUgZXhwbGFpbmVkIGJ5IGBncm9zc19zcXVhcmVfZmVldGAgaXMgMC42MTY2IGZvciB0aGUgY2xlYW5lZCBgTllDX2NvbmRvc2AuKiogVGhpcyBpcyBuZWFybHkgKipkb3VibGUqKiB0aGUgUi1zcXVhcmVkIHZhbHVlIGVzdGltYXRlZCBmb3IgdGhlIGBOWUNfY29uZG9zX29yaWdpbmFsYCBkYXRhc2V0IGF0IDAuMzE3Ny4oQmlnZ2VyIHRoZSBSIHNxdWFyZWQgdmFsdWUsIGJldHRlciB0aGUgcG9zc2liaWxpdHkgb2YgZXhwbGFpbmluZyB0aGUgcmVhbCBzY2VuYXJpbykgDQoNCiMgVXBkYXRlZCBzY2F0dGVycGxvdCBmb3IgTllDX0NvbmRvcw0KDQpCZWxvdyBpcyB0aGUgdXBkYXRlZCBzY2F0dGVycGxvdCB0aGF0IHVzZXMgdGhlIGNsZWFuZWQgYE5ZQ19jb25kb3NgIGRhdGEuIEZvciB0aGUgQnJvb2tseW4gYm9yb3VnaCB3ZSBhcmUgYmV0dGVyIGFibGUgdG8gc2VlIHRoZSBzcHJlYWQgb2YgdGhlIGRhdGEgYW5kIGhvdyB0aGUgdHJlbmQgbGluZSBmaXRzIHRoZSBkYXRhIGJlY2F1c2Ugd2UgcmVtb3ZlZCB0aGUgXCQzMCBtaWxsaW9uIG91dGxpZXJzLiBUaGUgc2FtZSBpcyB0cnVlIGZvciB0aGUgTWFuaGF0dGFuIGJvcm91Z2ggYmVjYXVzZSB0aGUgJDIwMCBtaWxsaW9uIGVwZW5zaXZlIGhvdXNlIGFuZCBzb21lIGR1cGxpY2F0ZSBlbnRyZWlzIGZvciBtdWx0aXBsZSB1bml0IHNhbGVzIHdlcmUgcmVtb3ZlZC4NCg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gTllDX2NvbmRvcywgDQogICAgICAgYWVzKHggPSBncm9zc19zcXVhcmVfZmVldCwgeSA9IHNhbGVfcHJpY2UpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMpICsNCiAgZmFjZXRfd3JhcCh+IGJvcm91Z2gsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIkNvbmRvbWluaXVtIFNhbGUgUHJpY2UgaW4gTllDIEdlbmVyYWxseSBJbmNyZWFzZXMgd2l0aCBTaXplIiwNCiAgICAgICB4ID0gIlNpemUgKEdyb3NzIFNxdWFyZSBGZWV0KSIsDQogICAgICAgeSA9ICJTYWxlIFByaWNlIChVU0QpIikNCmBgYA0KDQoNCiMgTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWxzIGZvciBlYWNoIEJvcm91Z2ggLSBDb2VmZmljaWVudCBFc3RpbWF0ZXMNCg0KTm93IGxldCdzIGFwcGx5IHRoZSBgYnJvb21gIHdvcmtmbG93IHRvIGNvbXBhcmUgY29lZmZpY2llbnQgZXN0aW1hdGVzIGFjcm9zcyB0aGUgZml2ZSBib3JvdWdocy4gVGhlIGdlbmVyYWwgd29ya2Zsb3cgdXNpbmcgYnJvb20gYW5kIHRpZHl2ZXJzZSB0b29scyB0byBnZW5lcmF0ZSBtYW55IG1vZGVscyBpbnZvbHZlcyA0IHN0ZXBzOg0KDQoxLiBOZXN0IGEgZGF0YWZyYW1lIGJ5IGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgd2l0aCB0aGUgYG5lc3QoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHRpZHlyYCBwYWNrYWdlIC0gd2Ugd2lsbCBuZXN0IGJ5IGBib3JvdWdoYC4NCjIuIEZpdCBtb2RlbHMgdG8gbmVzdGVkIGRhdGFmcmFtZXMgd2l0aCB0aGUgYG1hcCgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgcHVycnJgIHBhY2thZ2UuDQozLiBBcHBseSB0aGUgYGJyb29tYCBmdW5jdGlvbnMgYHRpZHkoKWAsIGBhdWdtZW50KClgLCBhbmQvb3IgYGdsYW5jZSgpYCB1c2luZyBlYWNoIG5lc3RlZCBtb2RlbCAtIHdlJ2xsIHdvcmsgd2l0aCBgdGlkeSgpYCBmaXJzdC4NCjQuIFJldHVybiBhIHRpZHkgZGF0YWZyYW1lIHdpdGggdGhlIGB1bm5lc3QoKWAgZnVuY3Rpb24gLSB0aGlzIGFsbG93cyB1cyB0byBzZWUgdGhlIHJlc3VsdHMuDQoNCmBgYHtyfQ0KTllDX25lc3RlZCA8LSBOWUNfY29uZG9zICU+JSANCiAgZ3JvdXBfYnkoYm9yb3VnaCkgJT4lIA0KICBuZXN0KCkNCg0KcHJpbnQoTllDX25lc3RlZCkNCmBgYA0KDQpUaGUgYE5ZQ19jb25kb3NgIGRhdGFmcmFtZSB3YXMgY29sbGFwc2VkIGZyb20gNyw5NDYgb2JzZXJ2YXRpb25zIHRvIG9ubHkgNS4gVGhlIG5lc3RpbmcgcHJvY2VzcyBpc29sYXRlZCB0aGUgc2FsZSByZWNvcmRzIGZvciBlYWNoIGJvcm91Z2ggaW50byBzZXBhcmF0ZSBkYXRhZnJhbWVzLiANCg0KV2UgY2FuIGV4dHJhY3QgYW5kIGluc3BlY3QgdGhlIHZhbHVlcyBvZiBhbnkgbmVzdGVkIGRhdGFmcmFtZS4gQmVsb3cgaXMgYSBsb29rIGF0IHRoZSBmaXJzdCBzaXggcm93cyBmb3IgTWFuaGF0dGFuLg0KDQpgYGB7cn0NCiMgVmlldyBmaXJzdCBmZXcgcm93cyBmb3IgTWFuaGF0dGFuDQpoZWFkKE5ZQ19uZXN0ZWQkZGF0YVtbM11dKQ0KYGBgDQoNCg0KVGhlIG5leHQgc3RlcCBpbiB0aGUgcHJvY2VzcyBpcyB0byBmaXQgYSBsaW5lYXIgbW9kZWwgdG8gZWFjaCBpbmRpdmlkdWFsIGRhdGFmcmFtZS4gV2hhdCB0aGlzIG1lYW5zIGlzIHRoYXQgd2UgYXJlIGdlbmVyYXRpbmcgc2VwYXJhdGUgbGluZWFyIG1vZGVscyBmb3IgZWFjaCBib3JvdWdoIGluZGl2aWR1YWxseS4NCg0KDQpgYGB7cn0NCg0KIyBTdGVwIDI6IGZpdCBsaW5lYXIgbW9kZWxzIHRvIGVhY2ggYm9yb3VnaCwgaW5kaXZpZHVhbGx5DQpOWUNfY29lZmZpY2llbnRzIDwtIE5ZQ19uZXN0ZWQgJT4lIA0KICBtdXRhdGUobGluZWFyX21vZGVsID0gbWFwKC54ID0gZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLmYgPSB+bG0oc2FsZV9wcmljZSB+IGdyb3NzX3NxdWFyZV9mZWV0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLikpKQ0KDQpgYGANCg0KVGFraW5nIGEgbG9vayBhdCB0aGUgZGF0YSBzdHJ1Y3R1cmUgd2Ugc2VlIHRoYXQgd2UgaGF2ZSBhIG5ldyBsaXN0LWNvbHVtbiBjYWxsZWQgYGxpbmVhcl9tb2RlbGAgdGhhdCBjb250YWlucyBhIGxpbmVhciBtb2RlbCBvYmplY3QgZm9yIGVhY2ggYm9yb3VnaC4NCg0KYGBge3J9DQojIEluc3BlY3QgdGhlIGRhdGEgc3RydWN0dXJlDQpwcmludChOWUNfY29lZmZpY2llbnRzKQ0KYGBgDQoNCg0KV2UgY2FuIHZpZXcgdGhlIGxpbmVhciBtb2RlbGluZyByZXN1bHRzIGZvciBhbnkgb25lIG9mIHRoZSBuZXN0ZWQgb2JqZWN0cyB1c2luZyB0aGUgYHN1bW1hcnkoKWAgZnVuY3Rpb24uIEJlbG93IGFyZSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gc3RhdGlzdGljcyBmb3IgTWFuaGF0dGFuLg0KDQpgYGB7cn0NCiMgVmVyaWZ5IG1vZGVsIHJlc3VsdHMgZm9yIE1hbmhhdHRhbg0Kc3VtbWFyeShOWUNfY29lZmZpY2llbnRzJGxpbmVhcl9tb2RlbFtbM11dKQ0KYGBgDQoNCg0KQSBxdWljayBsb29rIGF0IHRoZSBSLXNxdWFyZWQgdmFsdWUgZm9yIHRoZSBNYW5oYXR0YW4gbGluZWFyIG1vZGVsKDAuNjMzOCkgaW5kaWNhdGVzIHRoYXQgYGdyb3NzX3NxdWFyZV9mZWV0YCBsb29rcyB0byBiZSBhICoqZmFpcmx5IGdvb2Qgc2luZ2xlIHByZWRpY3RvciBvZiBgc2FsZV9wcmljZWAuIEFsbW9zdCB0d28tdGhpcmRzIG9mIHRoZSB2YXJpYWJpbGl0eSB3aXRoIGBzYWxlX3ByaWNlYCBpcyBleHBsYWluZWQgYnkgYGdyb3NzX3NxdWFyZV9mZWV0YC4qKg0KDQpUaGUgbmV4dCBzdGVwIGlzIHRvIHRyYW5zZm9ybSB0aGVzZSBsaW5lYXIgbW9kZWwgc3VtbWFyeSBzdGF0aXN0aWNzIGludG8gYSB0aWR5IGZvcm1hdC4NCg0KYGBge3J9DQojIFN0ZXAgMzogZ2VuZXJhdGUgYSB0aWR5IGRhdGFmcmFtZSBvZiBjb2VmZmljaWVudCBlc3RpbWF0ZXMgdGhhdCBpbmNsdWRlcyBjb25maWRlbmNlIGludGVydmFscw0KbGlicmFyeShicm9vbSkNCmxpYnJhcnkocHVycnIpDQpOWUNfY29lZmZpY2llbnRzIDwtIE5ZQ19jb2VmZmljaWVudHMgJT4lDQogIG11dGF0ZSh0aWR5X2NvZWZmaWNpZW50cyA9IG1hcCgueCA9IGxpbmVhcl9tb2RlbCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZiA9IHRpZHksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFKSkNCk5ZQ19jb2VmZmljaWVudHMNCg0KYGBgDQoNCk5vdyB3ZSBoYXZlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBgdGlkeV9jb2VmZmljaWVudHNgIHRoYXQgY29udGFpbnMgdGlkeSBjb2VmZmljaWVudCBlc3RpbWF0ZXMgZm9yIGVhY2ggb2YgdGhlIGZpdmUgYm9yb3VnaHMuIFRoZXNlIHRpZHkgc3RhdGlzdGljcyBhcmUgY3VycmVudGx5IHN0b3JlZCBpbiBmaXZlIHNlcGFyYXRlIGRhdGFmcmFtZXMuIEJlbG93IGFyZSB0aGUgY29lZmZpY2llbnQgZXN0aW1hdGVzIGZvciBNYW5oYXR0YW4uDQoNCmBgYHtyfQ0KIyBJbnNwZWN0IHRoZSByZXN1bHRzIGZvciBNYW5oYXR0YW4NCnByaW50KE5ZQ19jb2VmZmljaWVudHMkdGlkeV9jb2VmZmljaWVudHNbWzNdXSkNCmBgYA0KDQpOb3cgd2UgY2FuIHVubmVzdCB0aGUgYHRpZHlfY29lZmZpY2llbnRzYCB2YXJpYWJsZSBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZSB0aGF0IGluY2x1ZGVzIGNvZWZmaWNpZW50IGVzdGltYXRlcyBmb3IgZWFjaCBvZiBOZXcgWW9yayBDaXR5J3MgZml2ZSBib3JvdWdocy4gDQoNCmBgYHtyfQ0KIyBTdGVwIDQ6IFVubmVzdCB0byBhIHRpZHkgZGF0YWZyYW1lIG9mIGNvZWZmaWNpZW50IGVzdGltYXRlcw0KTllDX2NvZWZmaWNpZW50c190aWR5IDwtIE5ZQ19jb2VmZmljaWVudHMgJT4lIA0KICBzZWxlY3QoYm9yb3VnaCwgdGlkeV9jb2VmZmljaWVudHMpICU+JSANCiAgdW5uZXN0KGNvbHMgPSB0aWR5X2NvZWZmaWNpZW50cykNCnByaW50KE5ZQ19jb2VmZmljaWVudHNfdGlkeSkNCmBgYA0KDQpXZSdyZSBtYWlubHkgaW50ZXJlc3RlZCBpbiB0aGUgc2xvcGUgd2hpY2ggZXhwbGFpbnMgdGhlIGNoYW5nZSBpbiB5IChzYWxlIHByaWNlKSBmb3IgZWFjaCB1bml0IGNoYW5nZSBpbiB4IChzcXVhcmUgZm9vdGFnZSkuIFdlIGNhbiBmaWx0ZXIgZm9yIHRoZSBzbG9wZSBlc3RpbWF0ZSBvbmx5IGFzIGZvbGxvd3MuDQoNCmBgYHtyfQ0KIyBGaWx0ZXIgdG8gcmV0dXJuIHRoZSBzbG9wZSBlc3RpbWF0ZSBvbmx5IA0KTllDX3Nsb3BlIDwtIE5ZQ19jb2VmZmljaWVudHNfdGlkeSAlPiUgICANCiAgZmlsdGVyKHRlcm0gPT0gImdyb3NzX3NxdWFyZV9mZWV0IikgJT4lIA0KICBhcnJhbmdlKGVzdGltYXRlKQ0KcHJpbnQoTllDX3Nsb3BlKQ0KYGBgDQoNCldlJ3ZlIGFycmFuZ2VkIHRoZSByZXN1bHRzIGluIGFzY2VuZGluZyBvcmRlciBieSB0aGUgc2xvcGUgZXN0aW1hdGUuIEZvciBlYWNoIG9mIHRoZSBmaXZlIGJvcm91Z2hzLCB0aGUgdC1zdGF0aXN0aWMgYW5kIHAtdmFsdWUgaW5kaWNhdGUgdGhhdCB0aGVyZSBpcyBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBzYWxlX3ByaWNlYCBhbmQgYGdyb3NzX3NxdWFyZV9mZWV0YC4gSW4gU3RhdGVuIElzbGFuZCwgYW4gaW5jcmVhc2UgaW4gc3F1YXJlIGZvb3RhZ2UgYnkgb25lIHVuaXQgaXMgZXN0aW1hdGVkIHRvIGluY3JlYXNlIHRoZSBzYWxlIHByaWNlIGJ5IGFib3V0IFwkMjg4LCBvbiBhdmVyYWdlLiBJbiBjb250cmFzdCwgaW4gTWFuaGF0dGFuLCBhbiBpbmNyZWFzZSBpbiB0b3RhbCBzcXVhcmUgZm9vdGFnZSBieSBvbmUgdW5pdCBpcyBlc3RpbWF0ZWQgdG8gcmVzdWx0IGluIGFuIGluY3JlYXNlIGluIHNhbGUgcHJpY2Ugb2YgYWJvdXQgXCQ0LDcyOCwgb24gYXZlcmFnZS4NCg0KIyBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbHMgZm9yIGVhY2ggQm9yb3VnaCAtIFJlZ3Jlc3Npb24gU3VtbWFyeSBTdGF0aXN0aWNzDQoNCk5vdyB3ZSB3aWxsIGFwcGx5IHRoZSBzYW1lIHdvcmtmbG93IHVzaW5nIGBicm9vbWAgdG9vbHMgdG8gZ2VuZXJhdGUgdGlkeSByZWdyZXNzaW9uIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgZWFjaCBvZiB0aGUgZml2ZSBib3JvdWdocy4gQmVsb3cgd2UgZm9sbG93IHRoZSBzYW1lIHByb2Nlc3MgYXMgd2Ugc2F3IHByZXZpb3VzbHkgd2l0aCB0aGUgYHRpZHkoKWAgZnVuY3Rpb24sIGJ1dCBpbnN0ZWFkIHdlIHVzZSB0aGUgYGdsYW5jZSgpYCBmdW5jdGlvbi4NCg0KYGBge3J9DQojIEdlbmVyYXRlIGEgdGlkeSBkYXRhZnJhbWUgb2YgcmVncmVzc2lvbiBzdW1tYXJ5IHN0YXRpc3RpY3MNCk5ZQ19zdW1tYXJ5X3N0YXRzIDwtIE5ZQ19jb25kb3MgJT4lIA0KICBncm91cF9ieShib3JvdWdoKSAlPiUgDQogIG5lc3QoKSAlPiUgDQogIG11dGF0ZShsaW5lYXJfbW9kZWwgPSBtYXAoLnggPSBkYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZiA9IH5sbShzYWxlX3ByaWNlIH4gZ3Jvc3Nfc3F1YXJlX2ZlZXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSAuKSkpICU+JQ0KICBtdXRhdGUodGlkeV9zdW1tYXJ5X3N0YXRzID0gbWFwKC54ID0gbGluZWFyX21vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gZ2xhbmNlKSkNCnByaW50KE5ZQ19zdW1tYXJ5X3N0YXRzKQ0KYGBgICANCg0KDQpOb3cgd2UgaGF2ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgYHRpZHlfc3VtbWFyeV9zdGF0c2AgdGhhdCBjb250YWlucyB0aWR5IHJlZ3Jlc3Npb24gc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBlYWNoIG9mIHRoZSBmaXZlIGJvcm91Z2hzIGluIE5ldyBZb3JrIENpdHkuIFRoZXNlIHRpZHkgc3RhdGlzdGljcyBhcmUgY3VycmVudGx5IHN0b3JlZCBpbiBmaXZlIHNlcGFyYXRlIGRhdGFmcmFtZXMuIEJlbG93IHdlIHVubmVzdCB0aGUgZml2ZSBkYXRhZnJhbWVzIHRvIGEgc2luZ2xlLCB0aWR5IGRhdGFmcmFtZSBhcnJhbmdlZCBieSAqKlItc3F1YXJlZCoqIHZhbHVlLg0KDQpgYGB7cn0NCiMgVW5uZXN0IHRvIGEgdGlkeSBkYXRhZnJhbWUgb2YNCk5ZQ19zdW1tYXJ5X3N0YXRzX3RpZHkgPC0gTllDX3N1bW1hcnlfc3RhdHMgJT4lIA0KICBzZWxlY3QoYm9yb3VnaCwgdGlkeV9zdW1tYXJ5X3N0YXRzKSAlPiUgDQogIHVubmVzdChjb2xzID0gdGlkeV9zdW1tYXJ5X3N0YXRzKSAlPiUgDQogIGFycmFuZ2Uoci5zcXVhcmVkKQ0KcHJpbnQoTllDX3N1bW1hcnlfc3RhdHNfdGlkeSkNCmBgYA0KDQoqKkxvb2tpbmcgYXQgdGhlIHItc3F1YXJlZCB2YWx1ZXMsIHdlIHNlZSB0aGF0IGZvciBzb21lIGJvcm91Z2hzIGxpa2UgQnJvbnggYW5kIE1hbmhhdHRhbiwgd2hlcmUgci1zcXVhcmVkIGlzIG1vcmUgdGhhbiAuNTUsIGNvbmNsdWRlcyB0aGF0IEdyb3NzX3NxdWFyZV9mZWV0IGlzIGEgZ29vZCBwcmVkaWN0b3IgZm9yIHNhbGVfcHJpY2UsIGJ1dCBpbiBvdGhlciBib3JvdWdocywgR3Jvb3Nfc3F1YXJlX2ZlZXQgbWlnaHQgbm90IGJlIGEgZ29vZCBwcmVkaWN0b3IgZm9yIHRoZSBzYWxlIHByaWNlIGR1ZSB0byB0aGVpciBsb3cgci1zcXVhcmVkIHZhbHVlLioqIA0KDQojIENvbmNsdXNpb24NCg0KT3VyIGFuYWx5c2lzIHNob3dlZCB0aGF0LCBpbiBnZW5lcmFsLCB0aGUgYGdyb3NzX3NxdWFyZV9mZWV0YCB2YXJpYWJsZSBpcyB1c2VmdWwgZm9yIGV4cGxhaW5pbmcsIG9yIGVzdGltYXRpbmcsIGBzYWxlX3ByaWNlYCBmb3IgY29uZG9taW5pdW1zIGluIE5ldyBZb3JrIENpdHkuIFdlIG9ic2VydmVkIHRoYXQgKipyZW1vdmluZyBtdWx0aS11bml0IHNhbGVzIGZyb20gdGhlIGRhdGFzZXQgaW5jcmVhc2VkIG1vZGVsIGFjY3VyYWN5LioqIFdpdGggbGluZWFyIG1vZGVscyBnZW5lcmF0ZWQgZm9yIE5ldyBZb3JrIENpdHkgYXMgYSB3aG9sZSwgYW5kIHdpdGggbGluZWFyIG1vZGVscyBnZW5lcmF0ZWQgZm9yIGVhY2ggYm9yb3VnaCBpbmRpdmlkdWFsbHksIHdlIG9ic2VydmVkICoqaW4gYWxsIGNhc2VzIHRoYXQgdGhlIHQtc3RhdGlzdGljIHdhcyBoaWdoIGVub3VnaCwgYW5kIHRoZSBwLXZhbHVlIHdhcyBsb3cgZW5vdWdoLCB0byBkZWNsYXJlIHRoYXQgdGhlcmUgaXMgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgZ3Jvc3Nfc3F1YXJlX2ZlZXRgIGFuZCBgc2FsZV9wcmljZWAuKioNCg0KRm9yIHRoZSBsaW5lYXIgbW9kZWxzIHRoYXQgd2UgZ2VuZXJhdGVkIGZvciBlYWNoIGluZGl2aWR1YWwgYm9yb3VnaCwgd2Ugb2JzZXJ2ZWQgYSB3aWRlIHJhbmdlIGluIHNsb3BlIGVzdGltYXRlcy4gKipUaGUgc2xvcGUgZXN0aW1hdGUgZm9yIE1hbmhhdHRhbiB3YXMgbXVjaCBoaWdoZXIgdGhhbiB0aGUgZXN0aW1hdGUgZm9yIGFueSBvZiB0aGUgb3RoZXIgYm9yb3VnaHMuKiogV2UgZGlkIG5vdCByZW1vdmUgdGhlIHJlY29yZC1zZXR0aW5nIFwkMjQwIG1pbGxpb24gcHJvcGVydHkgc2FsZSBmcm9tIHRoZSBkYXRhc2V0LCBidXQgZnV0dXJlIGFuYWx5c2lzIHNob3VsZCBpbnZlc3RpZ2F0ZSB0aGUgaW1wYWN0cyB0aGF0IHRoaXMgc2luZ2xlIGxpc3RpbmcgaGFzIG9uIG1vZGVsaW5nIHJlc3VsdHMuIA0KDQpGaW5hbGx5LCByZWdyZXNzaW9uIHN1bW1hcnkgc3RhdGlzdGljcyBpbmRpY2F0ZSB0aGF0IGBncm9zc19zcXVhcmVfZmVldGAgaXMgYSBiZXR0ZXIgc2luZ2xlIHByZWRpY3RvciBvZiBgc2FsZV9wcmljZWAgaW4gc29tZSBib3JvdWdocyB2ZXJzdXMgb3RoZXJzLiBGb3IgZXhhbXBsZSwgdGhlIFItc3F1YXJlZCB2YWx1ZSB3YXMgZXN0aW1hdGVkIGF0IGFwcHJveGltYXRlbHkgMC42MyBpbiBNYW5oYXR0YW4sIGFuZCAwLjU5IGluIEJyb29rbHluLCBjb21wYXJlZCB0byBhbiBlc3RpbWF0ZSBvZiBvbmx5IDAuMzUgaW4gUXVlZW5zLiBUaGVzZSBkaWZmZXJlbmNlcyBpbiBSLXNxdWFyZWQgY29ycmVzcG9uZCB3aXRoIHRoZSBzY2F0dGVycGxvdHMgZ2VuZXJhdGVkIGZvciBlYWNoIGJvcm91Z2g7ICoqdGhlIHN0cmVuZ3RoKiogb2Ygc2FsZSBwcmljZXMgdmVyc3VzIGdyb3NzIHNxdWFyZSBmZWV0IHdhcyBoaWdoZXIsIGFuZCB0aGUgZGlzcGVyc2lvbiAoc3ByZWFkKSwgd2FzIGxvd2VyIGZvciBNYW5oYXR0YW4gYW5kIEJyb29rbHluIGFzIGNvbXBhcmVkIHRvIFF1ZWVucyB3aGVyZSB0aGUgcmVsYXRpb25zaGlwIHdhcyBub3RpY2VhYmx5IHdlYWtlciBiZWNhdXNlIHRoZSBkYXRhIHdhcyBtb3JlIHNwcmVhZCBvdXQuDQoNCg0KDQo=